#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/spi/spi.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <asm/delay.h>

#include <mach/v3s_lock_board.h> // 定义struct spi_lcd_platform_data

struct ili9488_par {
	struct spi_device *spi_dev;
	struct spi_lcd_platform_data *lcd_pdata;
	struct fb_info *fb_infos;
	struct work_struct flush_work;
};

static void write_reg(struct ili9488_par *par, unsigned int count, ...)
{
	va_list args;
	unsigned int i;
	unsigned char *cmd;
	int dc_gpio = par->lcd_pdata->gpio_dc;
	struct spi_device *dev = par->spi_dev;

	cmd = kzalloc(count, GFP_KERNEL);
	if (cmd == NULL) {
		dev_err(&dev->dev, "Alloc cmd buffer error.\n");
		return;
	}

	va_start(args, count);
	for (i = 0; i < count; i++) {
		cmd[i] = (unsigned char)(va_arg(args, unsigned int) & 0xff);
	}
	va_end(args);

	gpio_direction_output(dc_gpio, 0); // A0/DC = 1:data, 0:command, 切到命令模式
	spi_write(dev, cmd, count);
	gpio_direction_output(dc_gpio, 1); // A0/DC = 1:data, 0:command, 切回数据模式

	kfree(cmd);
}
#define NUMARGS(...)  (sizeof((unsigned int[]){__VA_ARGS__}) / sizeof(unsigned int))
#define ili9488lcm_write_reg(par, ...) write_reg(par, NUMARGS(__VA_ARGS__), __VA_ARGS__)

static void ili9488lcm_write_gram(struct ili9488_par *par, unsigned char data[], unsigned int count)
{
	struct spi_device *dev = par->spi_dev;
	unsigned int pos, i, size;

	/*********************************** 备注 *******************************
	 * 因为当前SoC的SPI默认使用DMA传送数据，一次不能传输过多的数据，所以分包传输;
	 * 若需要其他平台需要优化，则可以尝试直接使用spi_write(dev, data, count);
	 ************************************************************************/
	pos = 0;
	size = count / 4;
	for (i = 0; i < 4; i++) {
		spi_write(dev, data + pos, size);
		pos += size;
	}
	size = count %4;
	if (size) {
		spi_write(dev, data + pos, size);
	}
}

static void ili9488lcm_update(struct work_struct *work)
{
	struct ili9488_par *par = container_of(work, struct ili9488_par, flush_work);
	u8 *vmem = par->fb_infos->screen_base;

	ili9488lcm_write_reg(par, 0x2A, 0x00, 0x00, ((320 >> 8) & 0xff), (320 & 0xff));   // 设置光标x位置
	ili9488lcm_write_reg(par, 0x2B, 0x00, 0x00, ((480 >> 8) & 0xff), (480 & 0xff));   // 设置光标y位置
	ili9488lcm_write_reg(par, 0x2C);   // 开始写GRAM
	ili9488lcm_write_gram(par, vmem, par->fb_infos->screen_size);
}

static void ili9488lcm_init(struct ili9488_par *par)
{
	int reset_gpio, rotate;

	reset_gpio = par->lcd_pdata->gpio_reset;
	rotate = par->lcd_pdata->rotate;

	gpio_set_value(reset_gpio, 1);

	ili9488lcm_write_reg(par, 0x01); // soft reset
	ili9488lcm_write_reg(par, 0xE0, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F); // Gama
	ili9488lcm_write_reg(par, 0xE1, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F); // Gama
	ili9488lcm_write_reg(par, 0xC0, 0x17, 0x15);
	ili9488lcm_write_reg(par, 0xC1, 0x41);
	ili9488lcm_write_reg(par, 0xC5, 0x00, 0x12, 0x80);
	ili9488lcm_write_reg(par, 0x36, 0x48);
	ili9488lcm_write_reg(par, 0x3A, 0x66);
	ili9488lcm_write_reg(par, 0xB0, 0x00);
	ili9488lcm_write_reg(par, 0xB1, 0xA0);
	ili9488lcm_write_reg(par, 0xB4, 0x02);
	ili9488lcm_write_reg(par, 0xB6, 0x02, 0x02, 0x3B);
	ili9488lcm_write_reg(par, 0xB7, 0xC6);
	ili9488lcm_write_reg(par, 0xF7, 0xA9, 0x51, 0x2C, 0x82);
	ili9488lcm_write_reg(par, 0x11); // Sleep out
	ili9488lcm_write_reg(par, 0x29); // Display on
//	ili9488lcm_write_reg(par, 0x2A, 0x00, 0x00, ((320 >> 8) & 0xff), (320 & 0xff));   // 设置光标x位置
//	ili9488lcm_write_reg(par, 0x2B, 0x00, 0x00, ((480 >> 8) & 0xff), (480 & 0xff));   // 设置光标y位置
//	ili9488lcm_write_reg(par, 0x2C);   // 开始写GRAM
}

/***************** Start: Framebuffer framework *****************/

static ssize_t ili9488_write(struct fb_info *fb_infos, const char __user *buf, size_t count, loff_t *ppos)
{
	struct ili9488_par *par = fb_infos->par;
	size_t ret;

	ret = fb_sys_write(fb_infos, buf, count, ppos);
	schedule_work(&par->flush_work);

	return ret;
}

static void ili9488_fillrect(struct fb_info *fb_infos, const struct fb_fillrect *rect)
{
	struct ili9488_par *par = fb_infos->par;

	cfb_fillrect(fb_infos, rect);
	schedule_work(&par->flush_work);
}

static void ili9488_copyarea(struct fb_info *fb_infos, const struct fb_copyarea *area)
{
	struct ili9488_par *par = fb_infos->par;

	cfb_copyarea(fb_infos, area);
	schedule_work(&par->flush_work);
}

static void ili9488_imageblit(struct fb_info *fb_infos, const struct fb_image *image)
{
	struct ili9488_par *par = fb_infos->par;

	cfb_imageblit(fb_infos, image);
	schedule_work(&par->flush_work);
}

static struct fb_ops ili9488_ops = {
	.owner	    	= THIS_MODULE,
	.fb_read	    = fb_sys_read,
	.fb_write	    = ili9488_write,
	.fb_fillrect	= ili9488_fillrect,
	.fb_copyarea	= ili9488_copyarea,
	.fb_imageblit	= ili9488_imageblit,
};

static struct fb_fix_screeninfo ili9488_fix = {
	.id             = "ILI9488 LCD",
	.type           = FB_TYPE_PACKED_PIXELS,
	.visual         = FB_VISUAL_TRUECOLOR,
	.xpanstep       = 0,
	.ypanstep       = 0,
	.ywrapstep      = 0,
	.line_length    = 320*3,
	.accel          = FB_ACCEL_NONE,
};

static struct fb_var_screeninfo ili9488_var = {
	.xres           = 320,
	.yres           = 480,
	.xres_virtual   = 320,
	.yres_virtual   = 480,
	.bits_per_pixel = 24,
	.red            = {16, 8, 0},
	.green          = { 8, 8, 0},
	.blue           = { 0, 8, 0},
	.left_margin    = 0,
	.right_margin   = 0,
	.upper_margin   = 0,
	.lower_margin   = 0,
	.vmode          = FB_VMODE_NONINTERLACED,
	.rotate         = FB_ROTATE_UR,
};

static void ili9488_fb_deferred_io(struct fb_info *fb_infos, struct list_head *pagelist)
{
	struct ili9488_par *par = fb_infos->par;
	printk("<1>======== %s ===========\n", __func__);

	schedule_work(&par->flush_work);
}

static struct fb_deferred_io ili9488_defio = {
	.delay = HZ / 4,
	.deferred_io = ili9488_fb_deferred_io,
};

static int ili9488_probe(struct spi_device *spi_dev)
{
	int ret = 0;
	struct fb_info *fb_infos;
	struct spi_lcd_platform_data *lcd_pdata = spi_dev->dev.platform_data;
	u8 __iomem *vmem;
	u32 vmem_size = 320*480*3;
	struct ili9488_par *par;

	ret = gpio_request_one(lcd_pdata->gpio_dc, GPIOF_OUT_INIT_HIGH, "ili9488-spi-dc");
	if (ret != 0) {
		dev_err(&spi_dev->dev, "Request GPIO-DC:%d error.\n", lcd_pdata->gpio_dc);
		goto err_request_dc;
	}
	ret = gpio_request_one(lcd_pdata->gpio_reset, GPIOF_OUT_INIT_HIGH, "ili9488-spi-reset");
	if (ret != 0) {
		dev_err(&spi_dev->dev, "Request GPIO-RESET:%d error.\n", lcd_pdata->gpio_reset);
		goto err_request_reset;
	}
	gpio_set_value(lcd_pdata->gpio_reset, 0); // 硬复位

	fb_infos = framebuffer_alloc(sizeof(struct ili9488_par), &spi_dev->dev);
	if (fb_infos == NULL) {
		dev_err(&spi_dev->dev, "Couldn't allocate framebuffer.\n");
		ret = -ENOMEM;
		goto err_alloc_framebuffer;
	}

	vmem = (u8 __force __iomem*) devm_kzalloc(&spi_dev->dev, vmem_size, GFP_KERNEL);
	if (vmem == NULL) {
		dev_err(&spi_dev->dev, "Couldn't allocate graphical memory.\n");
		ret = -ENOMEM;
		goto err_alloc_vmem;
	}

	fb_infos->screen_base = vmem;
	fb_infos->screen_size = vmem_size;
	fb_infos->fbops = &ili9488_ops;

	ili9488_var.rotate = lcd_pdata->rotate;
	fb_infos->var = ili9488_var;

	ili9488_fix.smem_start = (unsigned long)fb_infos->screen_base;
	ili9488_fix.smem_len = vmem_size;
	fb_infos->fix = ili9488_fix;

	par = fb_infos->par;
	par->spi_dev = spi_dev;
	par->lcd_pdata = lcd_pdata;
	par->fb_infos = fb_infos;
	ili9488lcm_init(par);

	fb_infos->fbdefio = &ili9488_defio;
	fb_deferred_io_init(fb_infos);

	ret = register_framebuffer(fb_infos);
	if (ret != 0) {
		dev_err(&spi_dev->dev, "Couldn't register the framebuffer: %d\n", ret);
		goto err_register_framebuffer;
	}

	lcd_pdata->priv_data = par;
	INIT_WORK(&par->flush_work, ili9488lcm_update);

	dev_info(&spi_dev->dev, "fb%d: %s registered, using %d bytes of video memory\n", fb_infos->node, fb_infos->fix.id, vmem_size);

	return ret;

err_register_framebuffer:
	fb_deferred_io_cleanup(fb_infos);
	devm_kfree(&spi_dev->dev, vmem);

err_alloc_vmem:
	framebuffer_release(fb_infos);

err_alloc_framebuffer:
	gpio_free(lcd_pdata->gpio_reset);

err_request_reset:
	gpio_free(lcd_pdata->gpio_dc);

err_request_dc:

	return ret;
}

static int ili9488_remove(struct spi_device *spi_dev)
{
	struct spi_lcd_platform_data *lcd_pdata = spi_dev->dev.platform_data;
	struct ili9488_par *par = lcd_pdata->priv_data;
	struct fb_info *fb_infos = par->fb_infos;

	devm_kfree(&spi_dev->dev, fb_infos->screen_base);
	unregister_framebuffer(fb_infos);
	framebuffer_release(fb_infos);
	fb_deferred_io_cleanup(fb_infos);

	gpio_free(lcd_pdata->gpio_reset);
	gpio_free(lcd_pdata->gpio_dc);

	dev_info(&spi_dev->dev, "removing ili9488-spi driver\n");

	return 0;
}

static void ili9488_shutdown(struct spi_device *spi_dev)
{
}

static int ili9488_suspend(struct spi_device *spi_dev, pm_message_t mesg)
{
	return 0;
}

static int ili9488_resume(struct spi_device *spi_dev)
{
	return 0;
}

static const struct spi_device_id ili9488_spi_id[] = {
	{"ili9488-spi", 0},
	{},
};

static struct spi_driver ili9488_driver = {
	.id_table = ili9488_spi_id,
	.driver   = {
		.name  = "ili9488-spi",
		.owner = THIS_MODULE,
	},
	.probe    = ili9488_probe,
	.remove   = ili9488_remove,
	.shutdown = ili9488_shutdown,
	.suspend  = ili9488_suspend,
	.resume   = ili9488_resume,
};

int __init ili9488_init(void)
{
	return spi_register_driver(&ili9488_driver);
}

void __exit ili9488_exit(void)
{
	spi_unregister_driver(&ili9488_driver);
}

module_init(ili9488_init);
module_exit(ili9488_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yuyaowen");

#if 0
/*******************************************************************
 *** linux-3.4/arch/arm/mach-sunxi/include/mach/v3s_lock_board.h ***
 *******************************************************************

#ifndef __V3S_LOCK_BOARD_H__
#define __V3S_LOCK_BOARD_H__

#include <linux/fb.h>

struct spi_lcd_platform_data {
        int gpio_dc;
        int gpio_reset;
        int rotate; // 旋转
        void *priv_data;
};
/******** 上方rotate 参数 **********
 * FB_ROTATE_UR  0 //正常显示
 * FB_ROTATE_CW  1 //顺时针转90度
 * FB_ROTATE_UD  2 //上下倒置
 * FB_ROTATE_CCW 3 //逆时针转90度
 ***********************************/

extern void v3s_lock_board_init(void);

#endif /* __V3S_LOCK_BOARD_H__ */

********************************************************************/
#endif